package gamebackend;


import java.util.ArrayDeque;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import framework.Color;
import framework.GraphicsWrapper;
import framework.KPool;
import framework.KPool.PoolObjectFactory;
import framework.Point;


/**
 * Captures the movement of a Ship in a series of points.  The Ship is assumed to be moving 
 * towards the top of the screen.  The x axis is always between the first and second point,
 * with the first point above the axis.  The second to last point may be as far as the length of the path
 * below the x axis and the last point may be beyond that.
 * </ br></ br>
 * Below is a diagram of a possible path, with o representing points.
 * <pre>
 *           o 1st point (most recent ship position, a negative y value)
 *  ---------------x axis
 *         o
 *         o
 *           o
 *              o
 *               o
 *               ---- path length below x axis
 *               o last point (oldest ship position, a positive y value)
 * </pre>
 * @author Jonathan Caddey
 */

public class FlightPath implements PoolObjectFactory<Point> {
	/**
	 * How far above the x axis the first point is.  Always greater than 0.
	 */
	private double my_offset;
	
	private boolean my_alive;
	/**
	 * Contains the points where the y values describe changes in position, not absolute values.
	 */
	// TODO this used to be arraydequeue but it requires a min android sdk version 9 (I'm requiring min 8.)
	// I can either keep it this way and take a performance hit or make my own queue.
	private final List<Point> my_points = new LinkedList<Point>();
	private final KPool<Point> my_pool;
	private final double my_length;
	private int my_width;
	private double my_actualLength;
	private ShipFlier my_flier;
	private final Ship my_ship;
	private final Block my_block;
	/**
	 * How long it's been since the last point was added.  This is used to prevent
	 * too many points from being added.
	 */
	private double my_yChange;
	private double my_minYChange = 7;
//	/**
//	 * How many points have been created with new--for profiling.
//	 */
//	private int my_totalCreated;
	private final Point my_point;
	
	/**
	 * @param block the Block created by this path.  Block.PRIMARY_PATH or Block.SECONDARY_PATH typically are used.
	 * @param length
	 * @param width
	 * @param ship
	 * @param flier
	 */
	public FlightPath(Block block, int length, int width, Ship ship) {
		my_alive = true;
		my_flier = new ShipFlier();
		my_ship = ship;
		my_flier.setShip(ship);
		my_length = length;
		my_width = width;
		my_block = block;
		my_pool = new KPool<>(this, 20);// this could be fine tuned
		my_point = new Point(0, 0);
		
		// addFirst
		final Point p = my_pool.newObject();
		p.set(my_ship.getXPosition(), 0);
		my_points.add(0, p);
		addNewPoints();
	}
	public FlightPath(Block block, FlightPath startFrom, int width) {
		my_pool = startFrom.my_pool;
		my_point = startFrom.my_point;
		my_alive = true;
		my_flier = new ShipFlier();
		my_offset = startFrom.my_offset;
		my_actualLength += my_offset;
		my_ship = startFrom.my_ship.getCopy();
		my_flier.setShip(my_ship);
		my_length = startFrom.my_length;
		my_width = width;
		// addFirst
		final Point p = my_pool.newObject();
		p.set(my_ship.getXPosition(), 0);
		my_points.add(0, p);
		my_block = block;
	}
	public ShipFlier getShipFlier() {
		return my_flier;
	}
	public double getShipYVelocity() {
		return my_ship.getYVelocity();
	}
	public Block getBlock() {
		return my_block;
	}
	public int getWidth() {
		return my_width;
	}
	public void boost() {
		my_ship.boost();
	}
	
	/**
	 * Adds points at the start of the path until the first point is above
	 * the x axis.
	 */
	private void addNewPoints() {
		if (!my_alive) {
			return;
		}
		while (my_offset <= 0) {
			my_flier.getNextPosition(my_point);
			my_yChange += my_point.getY();
			if (my_yChange > my_minYChange) {
				// addFirst
				final Point p = my_pool.newObject();
				p.set(my_point.getX(), my_yChange);
				my_points.add(0, p);
				my_offset += my_yChange;
				my_actualLength += my_yChange;
				my_yChange = 0;
			}
			
		}
	}
	
	
	public void step(double yChange) {
		my_offset -= yChange;
		addNewPoints();
		if (!my_alive && my_points.size() <= 2) {
			while (!my_points.isEmpty()) {
				my_pool.free(my_points.remove(my_points.size() - 1));
			}
			return;
		}
		
		// remove points at the end of the path if they are no longer needed.
		Point removed = null;
		
		while (my_actualLength - my_offset > my_length && my_points.size() > 1) {
			// removeLast()
			if (removed != null) {
				my_pool.free(removed);
			}
			removed = my_points.remove(my_points.size() - 1);
			// getLast()
			my_actualLength -= my_points.get(my_points.size() - 1).getY();
		}
		if (removed != null) {
			// getLast()
			my_actualLength += my_points.get(my_points.size() - 1).getY();
			// addLast()
			my_points.add(removed);
		}
	}
	
	public double getLength() {
		return my_length;
	}
	public boolean finish() {
		my_alive = false;
		return my_points.isEmpty();
	}
	
	
	// TODO this super won't be here.  Just for prototyping purposes.
	public void render(GraphicsWrapper g2) {
		g2.setColor(Color.RED);
		try {
			double y = getFirstY();
			for (int i = 0; i < my_points.size(); i++) {
				final Point point = my_points.get(i);
				y += point.getY();
				g2.fillOval((int) point.getX(), (int)-my_length + (int) y - 1, 3,
						3);
			}
		} catch (ConcurrentModificationException e) {}
		g2.setColor(Color.YELLOW);
		g2.fillOval((int) getXPosition(), (int)-my_length - 1, 3,
				3);
	}

	
	/**
	 * @return how many points are in this path.
	 */
	public int size() {
		return my_points.size();
	}
	/**
	 * Gets the point at the specific index.  See class documentation.  The x value
	 * is the actual x position, but the y value is the distance to the next higher
	 * indexed point.  The absolute y value of the first point is from getFirstY().
	 * @param the_index
	 * @param the_point
	 */
	public void get(final int the_index, final Point the_point) {
		final Point point = my_points.get(the_index);
		the_point.set(point.getX(), point.getY());
	}
	
	/**
	 * @return the absolute y value of the first point.
	 */
	public double getFirstY() {
		return -my_offset;
	}
	// how I used to iterate over the points.  this was dropped in favor of a more (hopefully) memory efficient way.
//	public Iterator<Point> iterator() {
//		return new Iterator<Point>() {
//			private Iterator<Point> my_iterator = my_points.iterator();
//			private double my_nextY = -my_offset;
//			
//			@Override
//			public boolean hasNext() {
//				return my_iterator.hasNext();
//			}
//
//			@Override
//			public Point next() {
//				Point point = my_iterator.next();
//				my_nextY += point.getY();
//				return new Point(point.getX(), my_nextY - point.getY());
//			}
//
//			@Override
//			public void remove() {
//				throw new UnsupportedOperationException();
//			}
//			
//		};
//	}
	
	public double getXPosition() {
		return my_ship.getXPosition();
	}
	public double getXVelocity() {
		return my_ship.getXVelocity();
	}
	
	public void setWidth(final int the_width) {
		my_width = the_width;
	}
	@Override
	public Point createObject() {
//		my_totalCreated++;
//		System.out.println("Created point " + my_totalCreated);
		return new Point(0, 0);
	}

}
